Skip to contentMethod: buildClone(Object, Field, Object, Descriptor)
1: /**
2: * Copyright (C) 2011 Czech Technical University in Prague
3: * <p>
4: * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
5: * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
6: * version.
7: * <p>
8: * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10: * details. You should have received a copy of the GNU General Public License along with this program. If not, see
11: * <http://www.gnu.org/licenses/>.
12: */
13: package cz.cvut.kbss.jopa.sessions;
14:
15: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
16: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
17: import cz.cvut.kbss.jopa.model.annotations.Types;
18: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
19: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
20:
21: import java.lang.reflect.Constructor;
22: import java.lang.reflect.Field;
23: import java.lang.reflect.InvocationTargetException;
24: import java.net.URI;
25: import java.security.AccessController;
26: import java.security.PrivilegedActionException;
27: import java.util.*;
28:
29: /**
30: * Special class for cloning collections. Introduced because some Java collection have no no-argument constructor and
31: * thus they must be cloned specially. NOTE: This class may be removed in case a better cloning mechanisms (namely
32: * database mappings and copy policies) is introduced.
33: *
34: * @author kidney
35: */
36: class CollectionInstanceBuilder extends AbstractInstanceBuilder {
37:
38: private static final Class<?> singletonListClass = Collections.singletonList(null).getClass();
39: private static final Class<?> singletonSetClass = Collections.singleton(null).getClass();
40: private static final Class<?> arrayAsListClass = Arrays.asList(new Object()).getClass();
41:
42: CollectionInstanceBuilder(CloneBuilderImpl builder, UnitOfWork uow) {
43: super(builder, uow);
44: this.populates = true;
45: }
46:
47: /**
48: * This method is the entry point for cloning the Java collections. It clones standard collections as well as
49: * immutable collections and singleton collections. </p>
50: * <p>
51: * Currently supported are List and Set.
52: *
53: * @param collection The collection to clone
54: * @return A deep clone of the specified collection
55: */
56: @Override
57: Object buildClone(Object cloneOwner, Field field, Object collection, Descriptor repository)
58: throws OWLPersistenceException {
59:• assert (collection instanceof Collection);
60: Collection<?> container = (Collection<?>) collection;
61:• if (container instanceof IndirectCollection<?>) {
62: container = (Collection<?>) ((IndirectCollection<?>) container)
63: .getReferencedCollection();
64: }
65: Collection<?> clone = cloneUsingDefaultConstructor(cloneOwner, field, container, repository);
66:• if (clone == null) {
67:• if (Collections.EMPTY_LIST == container) {
68: return Collections.EMPTY_LIST;
69: }
70:• if (Collections.EMPTY_SET == container) {
71: return Collections.EMPTY_SET;
72: }
73: Constructor<?> c;
74: Object element = container.iterator().next();
75: Object[] params = new Object[1];
76:• if (!CloneBuilderImpl.isPrimitiveOrString(element.getClass())) {
77: element = builder.buildClone(element, repository);
78:• if (element instanceof Collection || element instanceof Map) {
79: element = builder.createIndirectCollection(element, cloneOwner, field);
80: }
81: }
82: params[0] = element;
83:• if (singletonListClass.isInstance(container)) {
84: c = getFirstDeclaredConstructorFor(singletonListClass);
85:• } else if (singletonSetClass.isInstance(container)) {
86: c = getFirstDeclaredConstructorFor(singletonSetClass);
87:• } else if (arrayAsListClass.isInstance(container)) {
88: final List arrayList = new ArrayList<>(container.size());
89: cloneCollectionContent(cloneOwner, field, container, arrayList, repository);
90: c = getFirstDeclaredConstructorFor(ArrayList.class);
91: params[0] = arrayList;
92: } else {
93: throw new OWLPersistenceException("Encountered unsupported type of collection: "
94: + container.getClass());
95: }
96: try {
97:• if (!c.isAccessible()) {
98: c.setAccessible(true);
99: }
100: clone = (Collection<?>) c.newInstance(params);
101: } catch (InstantiationException | IllegalArgumentException | InvocationTargetException e) {
102: throw new OWLPersistenceException(e);
103: } catch (IllegalAccessException e) {
104: logConstructorAccessException(c, e);
105: try {
106: clone = (Collection<?>) AccessController
107: .doPrivileged(new PrivilegedInstanceCreator(c));
108: } catch (PrivilegedActionException ex) {
109: throw new OWLPersistenceException(ex);
110: }
111: }
112: }
113: clone = (Collection<?>) builder.createIndirectCollection(clone, cloneOwner, field);
114: return clone;
115: }
116:
117: /**
118: * Clones the specified collection using its default zero argument constructor. If the specified collection has none
119: * (e. g. like SingletonList), this method returns null.
120: *
121: * @param container The collection to clone.
122: * @return cloned collection
123: */
124: private Collection<?> cloneUsingDefaultConstructor(Object cloneOwner, Field field,
125: Collection<?> container, Descriptor repository) {
126: Class<?> javaClass = container.getClass();
127: Collection<?> result = createNewInstance(javaClass, container.size());
128: if (result != null) {
129: // Makes shallow copy
130: cloneCollectionContent(cloneOwner, field, container, result, repository);
131: }
132: return result;
133: }
134:
135: private Collection<?> createNewInstance(Class<?> type, int size) {
136: Object[] params = null;
137: Class<?>[] types = {int.class};
138: // Look for constructor taking initial size as parameter
139: Constructor<?> ctor = getDeclaredConstructorFor(type, types);
140: if (ctor != null) {
141: params = new Object[1];
142: params[0] = size;
143: } else {
144: ctor = DefaultInstanceBuilder.getDeclaredConstructorFor(type, null);
145: }
146: if (ctor == null) {
147: return null;
148: }
149: Collection<?> result = null;
150: try {
151: result = (Collection<?>) ctor.newInstance(params);
152: } catch (InstantiationException | InvocationTargetException | IllegalArgumentException e) {
153: throw new OWLPersistenceException(e);
154: } catch (IllegalAccessException e) {
155: logConstructorAccessException(ctor, e);
156: try {
157: result = (Collection<?>) AccessController
158: .doPrivileged(new PrivilegedInstanceCreator(ctor));
159: } catch (PrivilegedActionException ex) {
160: logPrivilegedConstructorAccessException(ctor, ex);
161: // Do nothing
162: }
163: }
164: return result;
165: }
166:
167: /**
168: * Clone all the elements in the collection. This will make sure that the cloning process creates a deep copy.
169: *
170: * @param source The collection to clone.
171: */
172: private void cloneCollectionContent(Object cloneOwner, Field field, Collection<?> source,
173: Collection<?> target, Descriptor descriptor) {
174: if (source.isEmpty()) {
175: return;
176: }
177: Collection<Object> tg = (Collection<Object>) target;
178: for (Object obj : source) {
179: if (obj == null) {
180: tg.add(null);
181: continue;
182: }
183: if (CloneBuilderImpl.isPrimitiveOrString(obj.getClass())) {
184: tg.addAll(source);
185: break;
186: }
187: Object clone;
188: if (builder.isTypeManaged(obj.getClass())) {
189: clone = uow.registerExistingObject(obj, descriptor);
190: } else {
191: clone = builder.buildClone(cloneOwner, field, obj, descriptor);
192: }
193: tg.add(clone);
194: }
195: }
196:
197: @Override
198: void mergeChanges(Field field, Object target, Object originalValue, Object cloneValue) {
199: assert (originalValue == null || originalValue instanceof Collection);
200: assert cloneValue instanceof Collection;
201:
202: Collection<Object> orig = (Collection<Object>) originalValue;
203: Collection<Object> clone = (Collection<Object>) cloneValue;
204: if (clone instanceof IndirectCollection) {
205: clone = ((IndirectCollection<Collection<Object>>) clone).getReferencedCollection();
206: }
207: if (originalValue == null) {
208: orig = (Collection<Object>) createNewInstance(clone.getClass(), clone.size());
209: if (orig == null) {
210: orig = createDefaultCollection(clone.getClass());
211: }
212: EntityPropertiesUtils.setFieldValue(field, target, orig);
213: }
214: orig.clear();
215: if (clone.isEmpty()) {
216: return;
217: }
218: for (Object cl : clone) {
219: orig.add(uow.contains(cl) ? builder.getOriginal(cl) : cl);
220: }
221: final Types types = field.getAnnotation(Types.class);
222: if (types != null) {
223: checkForNewTypes(orig);
224: }
225: }
226:
227: private Collection<Object> createDefaultCollection(Class<?> cls) {
228: if (Set.class.isAssignableFrom(cls)) {
229: return new HashSet<>();
230: } else if (List.class.isAssignableFrom(cls)) {
231: return new ArrayList<>();
232: } else {
233: throw new IllegalArgumentException("Unsupported type of collection: " + cls);
234: }
235: }
236:
237: /**
238: * Checks if new types were added to the specified collection. </p>
239: * <p>
240: * If so, they are added to the module extraction signature managed by Metamodel.
241: *
242: * @param collection The collection to check
243: * @see Types
244: */
245: private void checkForNewTypes(Collection<?> collection) {
246: assert collection != null;
247: if (collection.isEmpty()) {
248: return;
249: }
250: final Set<URI> signature = builder.getMetamodel().getModuleExtractionExtraSignature();
251: for (Object elem : collection) {
252: final URI u = EntityPropertiesUtils.getValueAsURI(elem);
253: if (!signature.contains(u)) {
254: builder.getMetamodel().addUriToModuleExtractionSignature(u);
255: }
256: }
257: }
258: }